;;; Code:

;; -------- Config for company-mode --------
;; via: LazyCat & seagle0128
(use-package company
  :diminish company-mode
  :defer t
  :defines
  (company-dabbrev-ignore-case company-dabbrev-downcase)
  :commands
  company-cancel
  :bind
  (("M-/" . hippie-expand)
   ("C-M-i" . company-complete)
   :map company-mode-map
   ("<backtab>" . company-yasnippet)
   :map company-active-map
   ("C-p" . company-select-previous)
   ("C-n" . company-select-next)
   ;; ("<tab>" . company-complete-common-or-cycle)
   ("<tab>" . smarter-yas-expand-next-field-complete)
   ("<backtab>" . con5-company-yasnippet)
   :map company-search-map
   ("C-p" . company-select-previous)
   ("C-n" . company-select-next))
  :hook
  (;; ((after-init) . global-company-mode)
   (prog-mode . company-mode)
   (conf-mode . company-mode)
   (eshell-mode . company-mode)
   ((ess-mode inferior-ess-mode) . company-mode))
  :init
  (defun con5-company-yasnippet ()
    "Hide the current completeions and show snippets."
    (interactive)
    (company-cancel)
    (call-interactively 'company-yasnippet))
  (defun smarter-yas-expand-next-field-complete ()
    "Try to `yas-expand' and `yas-next-field' at current cursor position.
If failed try to complete the common part with `company-complete-common'"
    (interactive)
    (if yas-minor-mode
        (let ((old-point (point))
              (old-tick (buffer-chars-modified-tick)))
          (yas-expand)
          (when (and (eq old-point (point))
                     (eq old-tick (buffer-chars-modified-tick)))
            (ignore-errors (yas-next-field))
            (when (and (eq old-point (point))
                       (eq old-tick (buffer-chars-modified-tick)))
              (company-complete-common-or-cycle))))
      (company-complete-common-or-cycle)))
  (setq company-tooltip-align-annotations t
        company-tooltip-limit 12
        company-echo-delay (if (display-graphic-p) nil 0)
        company-minimum-prefix-length 2
        company-require-match nil
        company-global-modes '(not erc-mode message-mode help-mode
                                   gud-mode eshell-mode shell-mode))
  :custom
  (company-abort-manual-when-too-short t)
  (company-global-modes '(not dired-mode dired-sidebar-mode))
  :config
  (add-to-list 'company-backends 'company-files)
  (add-to-list 'company-backends 'company-capf)
  (add-to-list 'company-backends 'company-keywords)
  ;; ;; Add `company-elisp' backend for elisp.
  ;; (add-hook 'emacs-lisp-mode-hook
  ;;           (lambda ()
  ;;             (require 'company-elisp)
  ;;             (push 'company-elisp company-backends)))
  ;; `yasnippet' integration
  (with-no-warnings
    (with-eval-after-load 'yasnippet
      (defun company-backend-with-yas (backend)
        "Add `yasnippet' to company backend."
        (if (and (listp backend) (member 'company-yasnippet backend))
            backend
          (append (if (consp backend) backend (list backend))
                  '(:with company-yasnippet))))
      (defun my-company-enbale-yas (&rest _)
        "Enable `yasnippet' in `company'."
        (setq company-backends (mapcar #'company-backend-with-yas company-backends)))
      ;; Enable in current backends
      (my-company-enbale-yas)
      (defun my-company-yasnippet-disable-inline (fun command &optional arg &rest _ignore)
        "Enable yasnippet but disable it inline."
        (if (eq command 'prefix)
            (when-let ((prefix (funcall fun 'prefix)))
              (unless (memq (char-before (- (point) (length prefix)))
                            '(?. ?< ?> ?\( ?\) ?\[ ?{ ?} ?\" ?' ?`))
                prefix))
          (progn
            (when (and (bound-and-true-p lsp-mode)
                       arg (not (get-text-property 0 'yas-annotation-patch arg)))
              (let* ((name (get-text-property 0 'yas-annotation arg))
                     (snip (format "%s (Snippet)" name))
                     (len (length arg)))
                (put-text-property 0 len 'yas-annotation snip arg)
                (put-text-property 0 len 'yas-annotation-patch t arg)))
            (funcall fun command arg))))
      (advice-add #'company-yasnippet :around #'my-company-yasnippet-disable-inline)))
  (add-to-list 'company-transformers #'delete-dups))

;; ;; yasnippet support for all company backends
;; (defvar company-mode/enable-yas t
;;   "Enable yasnippet for all backends.")
;; (defun company-mode/backend-with-yas (backend)
;;   "Company BACKEND with yas."
;;   (if (or (not company-mode/enable-yas)
;; 	      (and (listp backend)
;; 	           (member 'company-yasnippet backend)))
;;       backend
;;     (append (if (consp backend) backend (list backend))
;; 	        '(:with company-yasnippet))))

(use-package company-box
  :defer t
  :diminish company-box-mode
  :hook
  (company-mode . company-box-mode))
(use-package company-posframe
  :defer t
  :diminish company-posframe-mode
  :hook
  (company-mode . company-posframe-mode))

;; Better sorting and filtering
(use-package prescient
  :defer t
  :init
  (setq prescient-filter-method 'fuzzy))
(use-package company-prescient
  :after company
  :hook
  (company-mode . company-prescient-mode)
  :init
  (company-prescient-mode 1)
  (setq company-prescient-sort-length-enable t))

;; ;; -------- company-dict --------
;; (use-package company-dict
;;   :config
;;   ;; Where to look for dictionary files. Default is ~/.emacs.d/dict
;;   (setq company-dict-dir (concat user-emacs-directory "dicts/"))
;;   ;; make company-dict aware of minor mode dictionaries
;;   ;; (setq company-dict-minor-mode-list '(cdis-mode))
;;   ;; Optional: if you want it available everywhere
;;   (add-to-list 'company-backends 'company-dict)
;;   (add-to-list 'company-dict-minor-mode-list 'cdisc-mode))

;; -------- yasnippet --------
(use-package yasnippet
  :defer t
  :diminish yas-minor-mode
  :init
  (use-package yasnippet-snippets
    :defer t
    :after yasnippet)
  :hook
  ((prog-mode markdown-mode LaTeX-mode org-mode text-mode ess-mode) . yas-minor-mode)
  :config
  ;; (add-hook 'emacs-startup-hook
  ;;           (lambda ()
  ;;             ;; global mode will delay init time
  ;;             (yas-global-mode 1)))
  (use-package ivy-yasnippet
    :defer t
    :bind
    (("C-c y" . 'ivy-yasnippet)))
  ;; load the snippet tables and then call `yas-minor-mode' from the hooks of major-modes where you want YASnippet enabled.
  (yas-reload-all)
  :bind
  (:map yas-keymap
        (;; ("TAB" . 'yas-next-field-or-maybe-expand)
         ;; ([(tab)] . 'yas-next-field-or-maybe-expand)
         ("<return>" . 'company-complete-selection))))

;; -------- update ESS for R --------
;; company with tabnine & R
;; via: github.com/emacs-ess/ESS/issues/955
(use-package ess-site
  :config
  (setq ess-r-company-backends
        '((company-tabnine
           company-R-library
           company-R-args
           company-R-objects
           company-dabbrev-code
           :separate))))

;; -------- expand <tab> with company yas --------
;; via: emacs.stackexchange.com/a/7925/12854
;; emacs.stackexchange.com/questions/7908/how-to-make-yasnippet-and-company-work-nicer
(defun check-expansion ()
  (save-excursion
    (if (looking-at "\\_>") t
      (backward-char 1)
      (if (looking-at "\\.") t
        (backward-char 1)
        (if (looking-at "->") t nil)))))

(defun do-yas-expand ()
  (let ((yas/fallback-behavior 'return-nil))
    (yas/expand)))

(defun tab-indent-or-complete ()
  (interactive)
  (cond
   ((minibufferp)
    (minibuffer-complete))
   (t
    (indent-for-tab-command)
    (if (or (not yas/minor-mode)
            (null (do-yas-expand)))
        (if (check-expansion)
            (progn
              (company-manual-begin)
              (if (null company-candidates)
                  (progn
                    (company-abort)
                    (indent-for-tab-command)))))))))

(defun tab-complete-or-next-field ()
  (interactive)
  (if (or (not yas/minor-mode)
          (null (do-yas-expand)))
      (if company-candidates
          (company-complete-selection)
        (if (check-expansion)
            (progn
              (company-manual-begin)
              (if (null company-candidates)
                  (progn
                    (company-abort)
                    (yas-next-field))))
          (yas-next-field)))))

(defun expand-snippet-or-complete-selection ()
  (interactive)
  (if (or (not yas/minor-mode)
          (null (do-yas-expand))
          (company-abort))
      (company-complete-selection)))

(defun abort-company-or-yas ()
  (interactive)
  (if (null company-candidates)
      (yas-abort-snippet)
    (company-abort)))

(global-set-key [tab] 'tab-indent-or-complete)
(global-set-key (kbd "TAB") 'tab-indent-or-complete)
(global-set-key [(control return)] 'company-complete-common)

(define-key company-active-map [tab] 'expand-snippet-or-complete-selection)
(define-key company-active-map (kbd "TAB") 'expand-snippet-or-complete-selection)

(define-key yas-minor-mode-map [tab] nil)
(define-key yas-minor-mode-map (kbd "TAB") nil)

(define-key yas-keymap [tab] 'tab-complete-or-next-field)
(define-key yas-keymap (kbd "TAB") 'tab-complete-or-next-field)
(define-key yas-keymap [(control tab)] 'yas-next-field)
(define-key yas-keymap (kbd "C-g") 'abort-company-or-yas)


(provide '+company)

;;; +company.el ends here
